using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Collections;
using NovodexWrapper;

namespace marbletrack
{
    public class Physics
    {
        NxVec3 physicsGravity = new NxVec3(0, -9800f, 0);
        float timestep = 1.0f / 60.0f;

        NxPhysicsSDK physicsSDK = null;
        public NxPhysicsSDK PhysicsSDK
        {
            get { return physicsSDK; }
        }

        NxScene physicsScene = null;
        public NxScene PhysicsScene
        {
            get { return physicsScene; }
        }

        NxMaterial trackMaterial;
        public NxMaterial TrackMaterial
        {
            get { return trackMaterial; }
        }

        NxMaterial ballMaterial;
        public NxMaterial BallMaterial
        {
            get { return ballMaterial; }
        }

        NxMaterial boostMaterial;
        public NxMaterial BoostMaterial
        {
            get { return boostMaterial; }
        }

        NxMaterial blockMaterial;
        public NxMaterial BlockMaterial
        {
            get { return blockMaterial; }
        }

        public const ushort DEFAULT_GROUP = 0;
        public const ushort BALL_PHYSICS_GROUP = 1;
        public const ushort TRACKPIECE_PHYSICS_GROUP = 2;
        public const ushort TRACK_SUPPORT_PHYSICS_GROUP = 3;
        public const ushort BOOST_PHYSICS_GROUP = 4;
        public const ushort BLOCK_PHYSICS_GROUP = 5;

        public Physics()
        {
            //Initialize the physics Scene and SDK
            physicsSDK = NxPhysicsSDK.Create();
            physicsScene = physicsSDK.createScene(physicsGravity);

            //Setup Contact Reports
            physicsScene.setUserContactReport(new ContactReport());
            physicsScene.setActorGroupPairFlags(BALL_PHYSICS_GROUP, TRACKPIECE_PHYSICS_GROUP, (uint)NxContactPairFlag.NX_NOTIFY_FORCES | (uint)NxContactPairFlag.NX_NOTIFY_ON_START_TOUCH | (uint)NxContactPairFlag.NX_NOTIFY_ON_TOUCH | (uint)NxContactPairFlag.NX_NOTIFY_ON_END_TOUCH);
            physicsScene.setActorGroupPairFlags(BALL_PHYSICS_GROUP, BALL_PHYSICS_GROUP, (uint)NxContactPairFlag.NX_NOTIFY_FORCES | (uint)NxContactPairFlag.NX_NOTIFY_ON_START_TOUCH);
            PhysicsScene.setActorGroupPairFlags(BALL_PHYSICS_GROUP, BOOST_PHYSICS_GROUP, (uint)NxContactPairFlag.NX_NOTIFY_FORCES | (uint)NxContactPairFlag.NX_NOTIFY_ON_START_TOUCH | (uint)NxContactPairFlag.NX_NOTIFY_ON_TOUCH | (uint)NxContactPairFlag.NX_NOTIFY_ON_END_TOUCH);
            PhysicsScene.setActorGroupPairFlags(BALL_PHYSICS_GROUP, BLOCK_PHYSICS_GROUP, (uint)NxContactPairFlag.NX_NOTIFY_FORCES | (uint)NxContactPairFlag.NX_NOTIFY_ON_START_TOUCH);
            PhysicsScene.setActorGroupPairFlags(BLOCK_PHYSICS_GROUP, BLOCK_PHYSICS_GROUP, (uint)NxContactPairFlag.NX_NOTIFY_FORCES | (uint)NxContactPairFlag.NX_NOTIFY_ON_START_TOUCH);

            //Setup physics timing
            physicsScene.setTiming(timestep / 4.0f, 4, NxTimeStepMethod.NX_TIMESTEP_FIXED);

            //Set the skin width in mm
            physicsSDK.setParameter(NxParameter.NX_SKIN_WIDTH, 3f);

            //Create Materials for use in game
            CreateMaterials();
        }

        private void CreateMaterials()
        {
            //Create default material
            physicsScene.setDefaultMaterial(new NxMaterialDesc(0.3f, 0.1f, 0.0f));

            //Create block material
            NxMaterialDesc blockMaterialDesc = new NxMaterialDesc(0.3f, 0.4f, 0.0f);
            blockMaterial = physicsScene.createMaterial(blockMaterialDesc);

            //Create track material
            NxMaterialDesc trackMaterialDesc = new NxMaterialDesc(0.3f, 0.1f, 0.0f);
            trackMaterial = physicsScene.createMaterial(trackMaterialDesc);

            //Create ball materials
            NxMaterialDesc ballMaterialDesc = new NxMaterialDesc(0.5f, 0.5f, 0.5f);
            ballMaterial = physicsScene.createMaterial(ballMaterialDesc);

            //Create boost material to be applied to the upSlope (Extra friction so the ball grips better)
            NxMaterialDesc boostMaterialDesc = new NxMaterialDesc(1f, 1f, 0.0f);
            boostMaterial = physicsScene.createMaterial(boostMaterialDesc);
        }

        //Run the physics simulation
        public void tickPhysics()
        {
            physicsScene.simulate(timestep);        //Run the physics for timestep seconds
            physicsScene.flushStream();				//Flush any commands that haven't been run yet
            physicsScene.fetchResults(NxSimulationStatus.NX_RIGID_BODY_FINISHED, true);	//Get the results of the simulation which is required before the next call to simulate()
        }

        //Create the Ground Plane for things to fall on
        public void CreateGround()
        {
            // Create static ground plane using descriptors
            NxPlaneShapeDesc planeDesc = NxPlaneShapeDesc.Default;    //The default plane is the ground plane so no parameters need to be changed
            NxBodyDesc bodyDesc = null;                               //using a null bodyDesc makes the actor static
            NxActorDesc actorDesc = new NxActorDesc(planeDesc, bodyDesc, 1, NxMat34.Identity);
            NxActor planeActor = physicsScene.createActor(actorDesc);
        }

        //Creates a Triangle Mesh Actor
        public NxActor CreateTriangleMeshActor(VertexPositionNormalTexture[] VertexPositionNormalTextureBuffer, int[] indexBuffer, Matrix pose, ushort physicsGroup, bool flagKinematic, bool flagFlipNormals)
        {
            //Extract vertex and index buffers from the modelClass object
            NxVec3[] vertexBuffer = MathConversion.ExtractVertexBuffer(VertexPositionNormalTextureBuffer);

            //Setup material indices
            ushort[] materialIndices = new ushort[indexBuffer.Length / 3];
            for (int i = 0; i < materialIndices.Length; i++)
            {
                if (physicsGroup == TRACKPIECE_PHYSICS_GROUP)
                    materialIndices[i] = TrackMaterial.getMaterialIndex();
                else if (physicsGroup == BOOST_PHYSICS_GROUP)
                    materialIndices[i] = BoostMaterial.getMaterialIndex();
                else
                    materialIndices[i] = physicsScene.getDefaultMaterial().getMaterialIndex();
            }

            //Use mesh descriptors to construct physics mesh
            NxTriangleMeshDesc meshDesc = new NxTriangleMeshDesc();
            meshDesc.numVertices = vertexBuffer.Length;
            meshDesc.numTriangles = indexBuffer.Length / 3;
            unsafe
            {
                meshDesc.pointStrideBytes = sizeof(NxVec3);
                meshDesc.triangleStrideBytes = sizeof(int);
                meshDesc.materialIndexStride = sizeof(ushort);
            }
            meshDesc.setPoints(vertexBuffer, false);
            meshDesc.setTriangleIndices(indexBuffer, false);
            meshDesc.setMaterialIndices(materialIndices, false);
            meshDesc.FlagFlipNormals = flagFlipNormals;
            NxTriangleMesh mesh = NovodexUtil.createTriangleMesh(meshDesc);

            //Setup physics mesh shape descriptor for actor descriptor
            NxTriangleMeshShapeDesc meshShapeDesc = new NxTriangleMeshShapeDesc();
            meshShapeDesc.MeshData = mesh;
            //meshShapeDesc.meshFlags = (uint)NxMeshShapeFlag.NX_MESH_SMOOTH_SPHERE_COLLISIONS;

            //Initialize new body and actor descriptors
            NxBodyDesc bodyDesc = new NxBodyDesc();
            NxActorDesc actorDesc = new NxActorDesc();

            //Setup actor descriptor
            actorDesc.addShapeDesc(meshShapeDesc);
            actorDesc.BodyDesc = bodyDesc;
            actorDesc.density = 500;
            actorDesc.globalPose = MathConversion.ToNxMat34(pose);

            //Create actor and register it with physics scene
            NxActor meshActor = physicsScene.createActor(actorDesc);

            //NxActor meshActor = NovodexUtil.createTriangleMeshActor(physicsScene, mesh, MathConversion.ToNxMat34(transformMatrix_), 25);

            //Make the track piece appear static
            meshActor.FlagKinematic = flagKinematic;

            //Add to track piece actor group and shape group
            meshActor.setGroup(physicsGroup);

            return meshActor;
        }
    }
}
